【堆溢出】从一个例子学习 House of Force

【堆溢出】从一个例子学习 House of Forcet

来源:香依香偎@闻道解惑

House of Foce 是堆溢出在特定场景下的一种简单利用方式,通过一个例子来学习下。

首先看下运行环境, Ubuntu 16.04.1 LTS x64Ubuntu GLIBC 2.23-0ubuntu10 版本的 GLIBC

01-runtime

再看下源码,有四次 malloc() 调用,中间夹着一次模拟溢出的内存改写。

02-program

gdb,调试走起,我们一句一句的看。

03-gdb

第一个 malloc:16

现在是在调用第一个 malloc(16) 之前的状态。

04-before-first-malloc

可以看到,堆 heap 还没有分配出来(main_arenatop字段等于0vmmap 还没有 heap 的内存段)。

05-main_arena-vmmap

ni 执行 malloc(16)之后,返回值是 0x602010

06-601020

此时 main_arenatop 指针指向 0x602020,而vmmap 的 heap 段起始于 0x602000

07-main_arena-vmmap

看看 0x602000 起始的这段堆内存的情况。

08-602000

0x6020000x60201F 的这 32 字节内存,就是 malloc(16) 所占用的堆内存。其中,前 16 个字节(0x602000 ~ 0x60200F)是 GLIBC 管理的堆头,后 16 个字节(0x602010 ~ 0x60201F)是返回给程序使用的空间,所以 malloc(16) 的返回值就是 0x602010。而 main_arenatop 指针指向空闲堆块的起始地址 0x602020。示意图如下:

09-heap

模拟溢出的内存改写

接下来源码的 1011 两行,是模拟用溢出的方式修改空闲内存块的 size 大小为 全F

10-line-10-11

修改成功。

11-overflow-to-trunk-size

为什么要修改空闲内存块的大小为 全F?是为了下一步申请超大内存时,避免因为空闲内存块大小不够而返回失败。继续看源码的第 13 行,第二个malloc(),申请负数大小的内存。

12-line-13

第二个 malloc:-4128

从汇编可以看出,由于 malloc 的入参格式是正整数,因此程序运行时会将负数 -4128 转换成超大整数 0xFFFFFFFFFFFFEFE0.

13-minus-4128

我们计算一下,这一次堆块分配,从空闲堆块起始位置 0x602020 开始,加上 16 字节的堆头,再减去 4128 之后,应该是 0x601010

14-calculator

看看执行 malloc(-4128) 之后,main_arena 的 top 指针,果然指向了 0x601010

15-main_arena-after-second-malloc

0x601010 所在的区域,就是程序的 GOT 表。其中 0x601018libc_start_main() 函数的 GOT 表项地址,0x601020malloc() 函数的 GOT 表项地址。

16-got-table

也就意味着,堆块的内存分配已经被程序劫持到了 GOT 表中。此时堆块的示意图如下:

17-heap-image

第三个 malloc:16

第三个 malloc() 分为两步,首先是分配 16个字节,然后再向分配的内存中写入 main() 函数地址。

18-set-to-main

分配 16 个字节之后,main_arena的top指针是 0x602030,返回给程序的地址是 0x601020

19-malloc-16-third

注意到 0x601020 其实是 malloc()GOT 表项地址,现在被 malloc() 输出到了程序里。当源码中用 *(long *)p = (long)main; 来修改分配的内存时,我们其实是覆盖了 malloc() 函数的 GOT 表项值,也就是说, malloc() 函数被劫持成了 main() 函数!

20-hijacked-to-main

示意图如下

21-overflow-malloc-to-main

第四个 malloc:16

第四个 malloc() 就是分配 16 个字节。

22-malloc-fourth

但此时,malloc()GOT 表项值已经被劫持成了 main() 函数地址。我们按 si 单步调试 step into,会发现 rip 走进了 main() 函数的空间。

23-si

24-rip-to-main

程序的流程被成功劫持!

House of Force

回顾一下,这个程序是怎么做到劫持运行流程导致重入了 main() 函数?其实只做了两件事情:

  • 修改了空闲堆块的 size 字段,从而避免下一步空间不够
  • 控制了 malloc() 申请的字节数,从而分配了超大空间

这就是 House of Force 的堆溢出利用技术。通常,这种利用方式需要满足两个条件:

  • 需要存在溢出漏洞,攻击者可以控制空闲堆块的 size 字段
  • 攻击者可以控制 malloc 的字节数和 malloc 的调用次数

只要满足这些条件,就可以利用例子中的方法抬高或者压低空闲堆块的地址,从而获得任意地址写的机会。

当然,不同版本 GLIBC 的堆块分配和处理方法都略有差异,真实利用时还需要在对应版本的 GLIBC 上仔细分析。